Technotes
On Multiple Inheritance & HandleObjects
This Technote answers a common question about MPW C++: Why doesn't HandleObject support multiple inheritance?
To answer that question, this Note provides a brief overview of how multiple
inheritance is implemented in MPW C++.
This Technote is addressed primarily to C++ developers who are concerned about memory fragmentation.
About HandleObjectsBeginning with Version 3.0, MacApp switched the focus of its object memory management from a handle-based system to a pointer-based system. This change substantially improved execution speed, specifically because pointer-based objects avoid compaction delays.
Accordingly, Apple recommends using For large objects, handles have some significant advantages. For one thing, they minimize RAM usage by avoiding fragmentation. Also, some developers need to continue using handles in their existing code. Handles under Future Versions of Mac OSPlans for future versions of Mac OS call for handles to become less important. An improved implementation of virtual memory (VM) will alleviate the effects of fragmentation for objects larger than the size of a VM page (4K bytes) while increasing the duration of heap compaction due to page-swapping between disk and RAM.
Using HandleObjectsMPW C++ contains several extensions to standard C++ for supporting Macintosh programming. One such extension is the built-in classHandleObject . Objects of any class descended from HandleObject are allocated as handles in the heap. Your program may refer to one of these objects as if it were a simple pointer; the compiler takes care of the extra dereference required.
A
The nature of
Heap AllocationBecause each object is allocated as a handle, all objects must be allocated on the heap. ("Native" C++ objects can be allocated on the stack or in the static space as well.) Consequently, you always declare variables and parameters as pointers to an object of the class. For example:class TSample: public HandleObject {The error message the compiler generates in this case is:
At first, this message might seem strange because the last two lines seem to both declare objects. Actually, the first declaration is of a pointer to an object, not of the object itself.Handle ManipulationThe second restriction is that you must follow the usual rules for manipulating handles. In particular, you have to be careful about creating pointers toHandleObject data members, since the object might move if the heap is compacted. If you writelong *x = & (aSampleObject -> fData);then x becomes "stale," i.e., it has a valid address but doesn't point to where the programmer intends, if the object moves. The solution is to lock the object if there is a possibility that the heap may be compacted. Objects of HandleObject are allocated with a call to NewHandle , so you can use HLock and HUnlock (along with an appropriate type cast) to lock and unlock the object.Multiple InheritanceThe third restriction is that you cannot use multiple inheritance with aHandleObject . The reason behind this restriction is not obvious. To understand the reason, you must look at the implementation of multiple inheritance.Implementing Multiple InheritanceTo understand how multiple inheritance is implemented, one needs a simple example. Suppose you define two classes as follows:class TBaseA {If you were to look at objects of these classes (see Figure 1), you would find that in each case the object storage would contain four bytes for the C++ virtual table ( vtable ) and four bytes for the data member. Any code that accesses the data members (for example, TBaseB::SetVarB ) would do so using a fixed offset from the start of the object. (In the particular version of C++, this offset was 0; your offset may vary.) Figure 1 shows the layout of TBaseA and TBaseB objects.
class TDerived: public TBaseA, public TBaseB {In this case, an object of TDerived has the following layout, as shown in Figure 2:
![]() Figure 2. Layout of TDerived object
This is what you would expect.
This gives you a very basic idea of how C++ implements multiple inheritance. For more details, read "Multiple Inheritance for C++" by Bjarne Stroustrup in Proceedings EUUG Spring 1987 Conference, Helsinki. Impact on HandleObjectsEach member function of aHandleObject class expects to be passed a handle to the object, instead of a pointer; when multiple inheritance is used, the compiler sometimes has to pass a pointer to the middle of the object. Pointers into the middle of an object, even though (and especially because) they are implicit in this case, nevertheless present the same problem as pointers to object data members (as described earlier). The object's handle could be moved during heap compaction, rendering the pointer "stale."
Designing a new implementation of multiple inheritance that is compatible with a Ignore FragmentationFor the majority of today's machines and applications, the main reason to useHandleObject is for purposes of compatibility with code that expects handle objects. However, another valid reason is to reduce the chance of fragmentation that would result from using non-relocatable blocks. But even in applications for which fragmentation would otherwise be a critical concern, memory allocation patterns may be very predictable; fragmentation is less of an issue when all allocated blocks are of similar sizes. Abandoning Multiple InheritanceThe other alternative is to give up multiple inheritance. In most cases, this isn't as difficult as it sounds. The typical way you would do this is with a form of delegation. For example, you could rewrite the classTDerived as:class TSingleDerived: public TBaseA {In this case TSingleDerived inherits only from TBaseA , but includes an object of TBaseB as an data member. It also implements the virtual member function SetBaseB to call the function by the same name in the TBaseB class. (In effect, TSingleDerived delegates part of its implementation to TBaseB .)
There are advantages and disadvantages to this approach. The advantage is that it requires only single inheritance, yet you can still reuse the implementation of CaveatYou should realize that the multiple inheritance implementation described here costs some extra space, compared to a simpler implementation that does not support multiple inheritance (e.g., the implementation used for aHandleObject ). Each vtable is twice as large, and each virtual member function takes about 24 bytes, compared to 14. This is true even if you do not take advantage of multiple inheritance. For this reason, MPW C++ also contains a built-in class called SingleObject , whose objects can be allocated in the same way as normal C++ objects, but which only supports single inheritance.
Managing Many HandlesIf you're writing large, high-end applications, you may need to manage thousands of objects. The Macintosh Memory Managers slow down when required to deal with so many handles. If you're dealing with many handles, here are some important points to keep in mind:
HNoPurge on this list prior to a NewPtr -based compaction, then call HPurge on it after the compaction.SummaryYou cannot use aHandleObject with multiple inheritance because of the way multiple inheritance is implemented in MPW C++. Your alternatives are to give up one or the other: You can either use native C++ objects and let the objects fall where they may, or give up multiple inheritance and use a form of delegation.Further Reference
Change HistoryThis Technote was originally written in August, 1990.Technotes Previous Technote | Contents | Next Technote |